//
//  Copyright (C) 2009-2010 Vadim Ushakov
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  perfmon.c
//

/*
<help>

 -- perfmon --

Performance monitor.

Usage:
  perfmon

(Only available in graphics mode)

The perfmon program is an interactive utility to show values of kernel
performance counters.

</help>
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ailes/window.h>
#include <ailes/multitasker.h>
#include <ailes/memory.h>
#include <ailes/performance.h>
#include <ailes/misc.h>
#include <sys/vsh.h>

//#define SHOW_MAX_ITEMS 128

static int processId = 0;
static int privilege = 0;
static int counterListParamsSize = 0;
static listItemParameters *counterListParams = NULL;
static objectKey window = NULL;
static objectKey counterList = NULL;
static int stop = 0;
static int numListItems = 0;


typedef struct _perfCounter
{
	char*    name;
	int      index;
	int64_t  prevValue;
	int64_t  currValue;
	int64_t  deltaValue;
} perfCounter;

static char* buffNames = 0;
static int buffNamesSize = 0;
static int perfCountersSize = 0;
static int perfCountersLen = 0;
static perfCounter* perfCounters;
/*
__attribute__((format(printf, 1, 2)))
static void error(const char *format, ...)
{
	// Generic error message code for either text or graphics modes
  
	va_list list;
	char output[MAXSTRINGLENGTH];
  
	va_start(list, format);
	vsnprintf(output, MAXSTRINGLENGTH, format, list);
	va_end(list);

	windowNewErrorDialog(window, "Error", output);
}
*/
static void updateCounter(perfCounter* counter)
{
	if (!counter)
		return;

	if (!counter->name)
		return;

	if (counter->index < 0)
		counter->index = performanceCounterGetIndex(counter->name);

	if (counter->index < 0)
	{
		counter->prevValue = -1;
		counter->currValue = -1;
		counter->deltaValue = 0;
	}
	else
	{
		counter->prevValue = counter->currValue;
		counter->currValue = performanceCounterGetI64(counter->index);
		counter->deltaValue = counter->currValue - counter->prevValue;
	}
}

static int getUpdate(void)
{
	int status = 0;
	char *bufferPointer = NULL;
	int count;
	int rowcount = 0;

	for (count = 0; count < perfCountersLen; count ++)
	{
		updateCounter(&perfCounters[count]);
	}

	if (counterListParamsSize < perfCountersLen)
	{
		int size = perfCountersLen;
		listItemParameters *buff = realloc(counterListParams, size * sizeof(listItemParameters));
		if (!buff)
			return (status = -1);
		counterListParams = buff;
		counterListParamsSize = size;
	}

	for (count = 0; count < perfCountersLen; count ++)
	{
		perfCounter* counter = &perfCounters[count];

		if (counter->currValue == 0 && counter->deltaValue == 0)
			continue;

		memset(counterListParams[rowcount].text, ' ', WINDOW_MAX_LABEL_LENGTH);
		bufferPointer = counterListParams[rowcount].text;

		sprintf(bufferPointer, "%s", counter->name);
		bufferPointer[strlen(bufferPointer)] = ' ';

		if (counter->index >= 0)
		{
			sprintf((bufferPointer + 45), "%lli", counter->currValue);
			bufferPointer[strlen(bufferPointer)] = ' ';

			sprintf((bufferPointer + 60), "%lli", counter->deltaValue);
			bufferPointer[strlen(bufferPointer)] = ' ';
		}
		else
		{
			sprintf((bufferPointer + 45), "N/A (error %i)", counter->index);
			bufferPointer[strlen(bufferPointer)] = ' ';
		}

		rowcount++;
	}

	numListItems = rowcount;

	return (status = 0);
}


static int sortCounters(const void* a, const void* b)
{
	return strcmp(((perfCounter*)a)->name, ((perfCounter*)b)->name);
}


static int initCountersList(void)
{
	int status = 0;

	perfCountersSize = 0;
	perfCountersLen = 0;
	free(perfCounters);
	perfCounters = 0;

	while (1)
	{
		int ReqSize = performanceListNames(buffNames, buffNamesSize);
		if (ReqSize < 0)
		{
			status = ReqSize;
			goto out;
		}
		if (ReqSize < buffNamesSize)
			break;
		
		int size = ReqSize + 1;
		char* buff = realloc(buffNames, size);
		if (!buff)
		{
			status = -1;
			goto out;
		}
		buffNames = buff;
		buffNamesSize = size;
	}

	char* name = buffNames;
	while (*name)
	{
		if (perfCountersLen >= perfCountersSize - 1)
		{
			int size = perfCountersSize + 32;
			perfCounter* buff = realloc(perfCounters, size * sizeof(perfCounter));
			if (!buff)
			{
				status = -1;
				goto out;
			}
			perfCounters = buff;
			perfCountersSize = size;
		}
		perfCounters[perfCountersLen].name = name;
		perfCounters[perfCountersLen].index = -1;
		perfCountersLen++;
		name += strlen(name) + 1;
	}

	qsort(perfCounters, perfCountersLen, sizeof(perfCounter), sortCounters);

	out:
	return (status);
}

static void eventHandler(objectKey key, windowEvent *event)
{
	// Check for the window being closed by a GUI event.
	if ((key == window) && (event->type == EVENT_WINDOW_CLOSE))
	{
		stop = 1;
		windowGuiStop();
		windowDestroy(window);
	}

}


static void constructWindow(void)
{
	// If we are in graphics mode, make a window rather than operating on the
	// command line.

	componentParameters params;

	// Create a new window
	window = windowNew(processId, "Performance Monitor");
	if (window == NULL)
		return;

	bzero(&params, sizeof(componentParameters));
	params.gridWidth = 1;
	params.gridHeight = 1;
	params.padLeft = 5;
	params.padTop = 5;
	params.orientationX = orient_left;
	params.orientationY = orient_top;

	params.gridX = 0;
	params.gridY = 0;
	params.padBottom = 0;
	fontGetDefault(&(params.font));
	params.flags |= WINDOW_COMPFLAG_FIXEDHEIGHT;
	// Create the label of column headers for the list below
	windowNewTextLabel(window, "Counter                                      Value          Delta    ", &params);

	params.padBottom = 5;
	params.padRight  = 5;
	params.flags &= ~WINDOW_COMPFLAG_FIXEDHEIGHT;

	params.gridY += 1;
	counterList = windowNewList(window, windowlist_textonly, 20, 1, 0, counterListParams, numListItems, &params);

	// Register an event handler to catch window close events
	windowRegisterEventHandler(window, &eventHandler);

	windowSetVisible(window, 1);

	return;
}


int main(int argc, char *argv[])
{
	int status = 0;
	int guiThreadPid = 0;

	// Only work in graphics mode
	windowOnlyInGraphicsMode();

	processId = multitaskerGetCurrentProcessId();
	privilege = multitaskerGetProcessPrivilege(processId);
/*
	counterListParams = malloc(SHOW_MAX_ITEMS * sizeof(listItemParameters));

	if (counterListParams == NULL)
	{
		if (counterListParams)
			free(counterListParams);
		error("Error getting memory");
		return (status = ERR_MEMORY);
	}
*/
	initCountersList();

	status = getUpdate();
	if (status < 0)
	{
		free(counterListParams);
		errno = status;
		if (argc)
			perror(argv[0]);
		return (status);
	}

	// Make our window
	constructWindow();

	// Run the GUI
	guiThreadPid = windowGuiThread();

	while (!stop && multitaskerProcessIsAlive(guiThreadPid))
	{
		if (getUpdate() < 0)
			break;
		windowComponentSetData(counterList, counterListParams, numListItems);
		multitaskerWaitMcs(1000000);
	}

	free(counterListParams);
	return (status = 0);
}
